package com.zonski.jbots.engine;

/**
 * Class that detects collisions between entities
 */
public final class CollisionDetector 
{
    private static final Rectangle C_RECT = new Rectangle(0, 0, 0, 0);

    public static final Collision getCollision(Entity entity1, Rectangle entity1VelocityBounds, Entity entity2, Collision cached)
    {
        int collisionType = Math.max(entity1.collisionEffect.getCollisionType(entity1, entity2),
                entity2.collisionEffect.getCollisionType(entity2, entity1));

        if(collisionType == CollisionEffect.NO_COLLISIONS)
        {
            // neither effect is interested in interacting with the other
            return null;
        }

        // may be more efficient to work out the intersection of
        // the adjusted bounds

        Collision collision;
        Rectangle bounds1 = entity1.bounds;
        Rectangle bounds2 = entity2.bounds;
        if(bounds1.overlaps(bounds2))
        {
            CollisionData data1 = cached.collisionData1;
            data1.setData(entity2, bounds1.x, bounds1.y, Directions.NONE, 0, 0);

            CollisionData data2 = cached.collisionData2;
            data2.setData(entity1, bounds2.x, bounds2.y, Directions.NONE, 0, 0);

            collision = cached;
            collision.entity1 = entity1;
            collision.entity2 = entity2;
        }else{
            Rectangle moveBounds1;
            // normalise the velocities so that entity2 is standing still
            int dx;
            int dy;
            if(entity2.xVelocity == 0 && entity2.yVelocity == 0)
            {
                // this case is very common
                moveBounds1 = entity1VelocityBounds;
                dx = (entity1.xVelocity*entity1.stepsRemaining)/Entity.STARTING_STEPS;
                dy = (entity1.yVelocity*entity1.stepsRemaining)/Entity.STARTING_STEPS;
            }else{
                dx = (entity1.xVelocity*entity1.stepsRemaining -
                    entity2.xVelocity*entity2.stepsRemaining)/
                    Entity.STARTING_STEPS;
                dy = (entity1.yVelocity*entity1.stepsRemaining -
                    entity2.yVelocity*entity2.stepsRemaining)/
                    Entity.STARTING_STEPS;
                Rectangle.expand(bounds1, dx, dy, C_RECT);
                moveBounds1 = C_RECT;
            }
            Rectangle moveBounds2 = bounds2;
            if(moveBounds1.overlaps(moveBounds2))
            {
                if((dx != 0 || dy != 0) && collisionType == CollisionEffect.DETAILED_COLLISIONS)
                {
                    collision = getDetailedCollision(entity1, entity2, dx, dy, cached);
                }else{

                    CollisionData data1 = cached.collisionData1;
                    data1.setData(entity2, bounds1.x,
                            bounds1.y, Directions.NONE, 0, 0);
                    CollisionData data2 = cached.collisionData2;
                    data2.setData(entity1, bounds2.x,
                            bounds2.y, Directions.NONE, 0, 0);
                    collision = cached;
                    collision.entity1 = entity1;
                    collision.entity2 = entity2;
                }
            }else{
                collision = null;
            }
        }
        return collision;
    }

    /**
     * Checks collisions without checking the adjusted bounds of
     * the movement
     */
    private static final Collision getDetailedCollision(Entity entity1,
            Entity entity2, int dx, int dy, Collision cached)
    {
        Rectangle r1 = entity1.bounds;
        Rectangle r2 = entity2.bounds;

        int corner1X;
        int corner1Y;
        int corner2X;
        int corner2Y;
        int end2X;
        int end2Y;
        int xedge;
        int yedge;
        if(dx >= 0)
        {
            corner1X = r1.rightEdge;
            corner2X = r2.x;
            end2X = r2.rightEdge + r1.width;
            xedge = Directions.EAST;
        }else{
            corner1X = r1.x;
            corner2X = r2.rightEdge;
            end2X = r2.x - r1.width;
            xedge = Directions.WEST;
        }
        if(dy >= 0)
        {
            corner1Y = r1.bottomEdge;
            corner2Y = r2.y;
            end2Y = r2.bottomEdge + r1.height;
            yedge = Directions.SOUTH;
        }else{
            corner1Y = r1.y;
            corner2Y = r2.bottomEdge;
            end2Y = r2.y - r1.height;
            yedge = Directions.NORTH;
        }

        Collision collision;
        // find the intersections
        int x;
        if(dy != 0)
        {
            x = getXIntersection(corner1X, corner1Y, corner1X+dx, corner1Y+dy, 
                corner2X, end2X, corner2Y);
        }else{
            x = Integer.MIN_VALUE;
        }
        /*
        System.out.println("getXIntersection("+(corner1X)+","+(corner1Y)+
                ","+(corner1X+dx)+","+(corner1Y+dy)+","+corner2X+
                ","+end2X+","+corner2Y+") = "+x);
        */
        if(x != Integer.MIN_VALUE)
        {
            //System.out.println("intersection on y axis");
            int y = corner2Y;

            // adjust x and y to reflect the actual intersection point
            // because we are only doing intersections using deltas
            int diff = y - corner1Y;
            
            int cost1 = (entity1.stepsRemaining * diff)/dy;
            int cost2 = (entity2.stepsRemaining * diff)/dy;

            int xadj = 
            (entity2.xVelocity*entity2.stepsRemaining*diff)/
            (Entity.STARTING_STEPS*dy);
            int yadj = 
            (entity2.yVelocity*entity2.stepsRemaining*diff)/
            (Entity.STARTING_STEPS*dy);

            x += xadj;
            y += yadj;
            
            // TODO : the adjusted y value isn't always correct, 
            // however they are WRT the edge that collides
            int length;
            if(dx >= 0)
            {
                length = getOverlap(x, x-r1.width, r2.x, r2.rightEdge);
            }else{
                length = getOverlap(x, x+r1.width, r2.x, r2.rightEdge);
            }
            CollisionData data1 = cached.collisionData1;
            data1.setData(entity2, x, y, yedge, cost1, length);
            CollisionData data2 = cached.collisionData2;
            data2.setData(entity1, x, y,
                    Directions.getOppositeDirection(yedge), cost2, length);
            collision = cached;
            collision.entity1 = entity1;
            collision.entity2 = entity2;
        }else if(dx != 0){
            int y;
            y = getYIntersection(corner1X, corner1Y, corner1X+dx, corner1Y+dy,
                   corner2X, corner2Y, end2Y);
            /*
            System.out.println("getYIntersection("+corner1X+","+corner1Y+
                ","+(corner1X+dx)+","+(corner1Y+dy)+","+corner2X+
                ","+corner2Y+","+end2Y+") = "+y);
                */
            if(y != Integer.MIN_VALUE)
            {
                //System.out.println("intersection on x axis");
                x = corner2X;

                int diff = x - corner1X;

                int cost1 = (entity1.stepsRemaining * diff)/dx;
                int cost2 = (entity2.stepsRemaining * diff)/dx;

                int xadj = 
                (entity2.xVelocity*entity2.stepsRemaining*diff)/
                (Entity.STARTING_STEPS*dx);
                int yadj = 
                (entity2.yVelocity*entity2.stepsRemaining*diff)/
                (Entity.STARTING_STEPS*dx);

                x += xadj;
                y += yadj;


                // TODO : the adjusted x value isn't always correct, 
                // however they are WRT the edge that collides
                int length;
                if(dy >= 0)
                {
                    length = getOverlap(y, y-r1.height, r2.y, r2.bottomEdge);
                }else{
                    length = getOverlap(y, y+r1.height, r2.y, r2.bottomEdge);
                }
                CollisionData data1 = cached.collisionData1;
                data1.setData(entity2, x, y, xedge, cost1, length);
                CollisionData data2 = cached.collisionData2;
                data2.setData(entity1, x, y,
                    Directions.getOppositeDirection(xedge), cost2, length);
                collision = cached;
                collision.entity1 = entity1;
                collision.entity2 = entity2;
            }else{
                collision = null;
            }
        }else{
            collision = null;
        }
        return collision;
    }

    /**
     * Obtains the point of intersection
     * of these two lines, the second line is defined as flat along the 
     * x-axis. If there is no point of intersection, Integer.MIN_VALUE
     * is returned
     */
    public static final int getXIntersection(
            int x1a, int y1a, int x1b, int y1b,
            int x2a, int x2b, int y2)
    {
        int value;
        if(y1a == y1b || Math.min(y1a, y1b) > y2 || Math.max(y1a, y1b) < y2)
        {
            value = Integer.MIN_VALUE;
        }else{
            int y1diff = y1b - y1a;
            int x1diff = x1b - x1a;
            // obtain the point ratio
            int y2diff = y1b - y2;

            //System.out.println("y1diff = "+y1diff);
            //System.out.println("x1diff = "+x1diff);
            //System.out.println("y2diff = "+y2diff);
            
            value = x1a + (x1diff * y2diff) / y1diff;
            //System.out.println("value = "+value);

            if(value > Math.max(x2b, x2a))
            {
                value = Integer.MIN_VALUE;
            }else if(value < Math.min(x2b, x2a)){
                value = Integer.MIN_VALUE;
            }

        };
        return value;
    }

    public static final int getYIntersection(
            int x1a, int y1a, int x1b, int y1b,
            int x2, int y2a, int y2b)
    {
        return getXIntersection(y1a, x1a, y1b, x1b, y2a, y2b, x2);
    }

    /**
     * obtains the overlap between two lines defined by 
     * the four points
     */
    public static final int getOverlap(int p1a, int p1b, int p2a, int p2b)
    {
        int overlap;
        int min1 = Math.min(p1a, p1b);
        int max1 = Math.max(p1a, p1b);
        int min2 = Math.min(p2a, p2b);
        int max2 = Math.max(p2a, p2b);
        if(max1 < min2)
        {
            overlap = -1;
        }else if(max2 < min1){
            overlap = -1;
        }else{
            int secondMax = Math.min(max1, max2);
            int secondMin = Math.max(min1, min2);
            overlap = secondMax - secondMin;
        }
        return overlap;
    }
    
    private CollisionDetector()
    {
    }
}
